﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;

namespace MacomberMapSystem.Common.Internals
{
    /// <summary>
    /// This class provides hit testing between a line/rectangle and another line/rectangle.
    /// </summary>
    public static class MM_HitTesting
    {
        /// <summary>
        /// Determine whether two lines intersect each other
        /// </summary>
        /// <param name="L1Start"></param>
        /// <param name="L1End"></param>
        /// <param name="L2Start"></param>
        /// <param name="L2End"></param>
        /// <param name="Intersection">The point of intersection</param>
        /// <returns></returns>        
        public static bool CheckIntersection(PointD L1Start, PointD L1End, PointF L2Start, PointF L2End, out PointF Intersection)
        {

            //First, determine our slopes and y-intercepts
            float Slope1 = (float)((L1End.Y - L1Start.Y) / (L1End.X - L1Start.X));
            float YInt1 = (float)(L1Start.Y - (Slope1 * L1Start.X));

            float Slope2 = (L2End.Y - L2Start.Y) / (L2End.X - L2Start.X);
            float YInt2 = L2Start.Y - (Slope2 * L2Start.X);

            //Determine our intersection points
            Intersection = new PointF((YInt2 - YInt1) / (Slope1 - Slope2), 0);

            if (!float.IsNaN(Intersection.X))
                Intersection.Y = (float)Math.Round((Slope1 * Intersection.X) + YInt1);
            else if (!float.IsPositiveInfinity(Slope1) && !float.IsNegativeInfinity(Slope1))
            {
                Intersection.X = L2Start.X;
                Intersection.Y = (float)Math.Round((Slope1 * Intersection.X) + YInt1);
            }
            else if (!float.IsPositiveInfinity(Slope2) && !float.IsNegativeInfinity(Slope2))
            {
                Intersection.X = (float)L1Start.X;
                Intersection.Y = (float)Math.Round((Slope2 * Intersection.X) + YInt2);
            }
            else
            {
                //TODO: If we have two vertical lines, see whether they intersect                
                return false;
            }

            //Tally up our intersections, and determine whether there are any collisions
            int Tally = 0;
            Tally += IsBetween(Intersection.X, Intersection.Y, (float)L1Start.X, (float)L1Start.Y, (float)L1End.X, (float)L1End.Y) ? 1 : 0;
            Tally += IsBetween(Intersection.X, Intersection.Y, L2Start.X, L2Start.Y, L2End.X, L2End.Y) ? 1 : 0;
            return Tally > 1;
        }

        /// <summary>
        /// Determine whether two lines intersect each other
        /// </summary>
        /// <param name="L1Start"></param>
        /// <param name="L1End"></param>
        /// <param name="L2Start"></param>
        /// <param name="L2End"></param>
        /// <param name="Intersection">The point of intersection</param>
        /// <returns></returns>        
        public static bool CheckIntersection(PointF L1Start, PointF L1End, PointF L2Start, PointF L2End, out PointF Intersection)
        {

            //First, determine our slopes and y-intercepts
            float Slope1 = (L1End.Y - L1Start.Y) / (L1End.X - L1Start.X);
            float YInt1 = L1Start.Y - (Slope1 * L1Start.X);

            float Slope2 = (L2End.Y - L2Start.Y) / (L2End.X - L2Start.X);
            float YInt2 = L2Start.Y - (Slope2 * L2Start.X);

            //Determine our intersection points
            Intersection = new PointF((YInt2 - YInt1) / (Slope1 - Slope2),0);

            if (!float.IsNaN(Intersection.X))
                Intersection.Y = (float)Math.Round((Slope1 * Intersection.X) + YInt1);
            else if (!float.IsPositiveInfinity(Slope1) && !float.IsNegativeInfinity(Slope1))
            {
                Intersection.X = L2Start.X;
                Intersection.Y = (float)Math.Round((Slope1 * Intersection.X) + YInt1);
            }
            else if (!float.IsPositiveInfinity(Slope2) && !float.IsNegativeInfinity(Slope2))
            {
                Intersection.X = L1Start.X;
                Intersection.Y = (float)Math.Round((Slope2 * Intersection.X) + YInt2);
            }
            else
            {
                //TODO: If we have two vertical lines, see whether they intersect                
                return false;
            }

            //Tally up our intersections, and determine whether there are any collisions
            int Tally = 0;
            Tally += IsBetween(Intersection.X, Intersection.Y, L1Start.X, L1Start.Y, L1End.X, L1End.Y) ? 1 : 0;
            Tally += IsBetween(Intersection.X, Intersection.Y, L2Start.X, L2Start.Y, L2End.X, L2End.Y) ? 1 : 0;
            return Tally > 1;
        }

        
        /// <summary>
        /// Determine whether a line and rectangle intersect each other
        /// </summary>
        /// <param name="L1Start"></param>
        /// <param name="L1End"></param>
        /// <param name="Rect2"></param>
        /// <param name="Intersection"></param>
        /// <returns></returns>
        public static bool CheckIntersection(PointF L1Start, PointF L1End, RectangleF Rect2, out PointF Intersection)
        {

            //Line to left, top, right, bottom of rectangle
            if ((L1Start.X < Rect2.Left && L1End.X < Rect2.Left) ||(L1Start.Y < Rect2.Top && L1End.Y < Rect2.Top) || (L1Start.X > Rect2.Right && L1End.X > Rect2.Right) || (L1Start.Y > Rect2.Bottom && L1End.Y > Rect2.Bottom))
            {
                Intersection = PointF.Empty;
                return false;
            }
            
            //First point within rectangle
            else if (L1Start.X >= Rect2.Left && L1Start.X <= Rect2.Right && L1Start.Y >= Rect2.Top && L1Start.Y <= Rect2.Bottom)
            {
                Intersection = new PointF(L1Start.X, L1Start.Y);
                return true;
            }
            //Second point within rectangle
            else if (L1End.X >= Rect2.Left && L1End.X <= Rect2.Right && L1End.Y >= Rect2.Top && L1End.Y <= Rect2.Bottom)
            {
                Intersection = new PointF(L1End.X, L1End.Y);
                return true;
            }

            //Line intersects rectangle left            
            else if (CheckIntersection(L1Start, L1End, Rect2.Location, new PointF(Rect2.Left, Rect2.Bottom), out Intersection))
                return true;

            //Line intersects rectangle right
            else if (CheckIntersection(L1Start, L1End, new PointF(Rect2.Right, Rect2.Top), new PointF(Rect2.Right, Rect2.Bottom), out Intersection))
                return true;

            //Line intersects rectangle top
            else if (CheckIntersection(L1Start, L1End, Rect2.Location, new PointF(Rect2.Right, Rect2.Top), out Intersection))
                return true;

            //Line intersects rectangle bottom
            else if (CheckIntersection(L1Start, L1End, new PointF(Rect2.Left, Rect2.Bottom), new PointF(Rect2.Right, Rect2.Bottom), out Intersection))
                return true;

            //Unknown reason why not intersecting
            else
                return false;
        }

        
        /// <summary>
        /// Determine whether a rectangle and line intersect each other 
        /// </summary>
        /// <param name="Rect1"></param>
        /// <param name="L2Start"></param>
        /// <param name="L2End"></param>
        /// <param name="Intersection"></param>
        /// <returns></returns>
        public static bool CheckIntersection(RectangleF Rect1, PointF L2Start, PointF L2End,  out PointF Intersection)
        {
            return CheckIntersection(L2Start, L2End, Rect1, out Intersection);
        }

        /// <summary>
        /// Determine whether two rectangles intersect each other
        /// </summary>
        /// <param name="Rect1"></param>
        /// <param name="Rect2"></param>
        /// <param name="Intersection"></param>
        /// <returns></returns>
        public static bool CheckIntersection(RectangleF Rect1, RectangleF Rect2, out PointF Intersection)
        {
            RectangleF Out = Rect1;
            Out.Intersect(Rect2);
            Intersection = new PointF(Out.Left + (Out.Width / 2f), Out.Top + (Out.Height / 2f));
            return Rect1.IntersectsWith(Rect2);
        }

        /// <summary>
        /// Determine whether a rectangle intersects a graphics path
        /// </summary>
        /// <param name="Rect1"></param>
        /// <param name="Path2"></param>
        /// <param name="Intersection"></param>
        /// <returns></returns>
        public static bool CheckIntersection(RectangleF Rect1, GraphicsPath Path2, out PointF Intersection)
        {
            foreach (KeyValuePair<PointF, PointF> Points2 in ExtractLines(Path2))
                if (CheckIntersection(Points2.Key, Points2.Value, Rect1, out Intersection))
                    return true;
            Intersection = PointF.Empty;
            return false;
        }

        /// <summary>
        /// Determine whether a line and 
        /// </summary>
        /// <param name="Coordinates"></param>
        /// <param name="Bounds"></param>
        /// <param name="Intersection"></param>
        /// <returns></returns>
        public static bool CheckIntersection(PointD[] Coordinates, PointF[] Bounds, out PointF Intersection)
        {
            for (int a = 0; a < Coordinates.Length - 1; a++)
                for (int b = 0; b < Bounds.Length - 1; b++)
                    if (CheckIntersection(Coordinates[a], Coordinates[a + 1], Bounds[b], Bounds[b + 1], out Intersection))
                        return true;
            Intersection = PointF.Empty;
            return false;
        }

        /// <summary>
        /// Determine whether two graphics paths intersect
        /// </summary>
        /// <param name="Path1"></param>
        /// <param name="Path2"></param>
        /// <param name="Intersection"></param>
        /// <returns></returns>
        public static bool CheckIntersection(GraphicsPath Path1, GraphicsPath Path2, out PointF Intersection)
        {
            foreach (KeyValuePair<PointF, PointF> Points1 in ExtractLines(Path1))
                foreach (KeyValuePair<PointF, PointF> Points2 in ExtractLines(Path2))
                    if (CheckIntersection(Points1.Key, Points1.Value, Points2.Key, Points2.Value, out Intersection))
                        return true;
                Intersection = PointF.Empty;
            return false;
        }

        /// <summary>
        /// Determine whether a particular point is in between two others
        /// </summary>
        /// <param name="XInt"></param>
        /// <param name="YInt"></param>
        /// <param name="X1"></param>
        /// <param name="Y1"></param>
        /// <param name="X2"></param>
        /// <param name="Y2"></param>
        /// <returns></returns>
        private static bool IsBetween(float XInt, float YInt, float X1, float Y1, float X2, float Y2)
        {
            return (XInt >= Math.Min(X1, X2) && XInt <= Math.Max(X1, X2) && YInt >= Math.Min(Y1, Y2) && YInt <= Math.Max(Y1, Y2));
        }

        /// <summary>
        /// Extract the collection of lines to check from a graphics path
        /// </summary>
        /// <param name="gp"></param>
        /// <returns></returns>
        public static List<KeyValuePair<PointF, PointF>> ExtractLines(GraphicsPath gp)
        {
            List<KeyValuePair<PointF, PointF>> OutVals = new List<KeyValuePair<PointF, PointF>>();
            for (int a = 1; a < gp.PointCount; a++)
                if (((PathPointType)gp.PathTypes[a-1] & PathPointType.CloseSubpath) != PathPointType.CloseSubpath)
                OutVals.Add(new KeyValuePair<PointF, PointF>(gp.PathPoints[a - 1], gp.PathPoints[a]));            
            return OutVals;
        }
    }
}
